package com.wangshouyu.mp.base.request;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.util.*;

import static java.lang.String.format;

/**
 * @author 王守钰
 * @date 2020年12月04日 19:51
 * @description 微信公众号url connection客户端
 */
public class MpUrlConnectionClient implements MpClient{

    private Logger log = LoggerFactory.getLogger(MpUrlConnectionClient.class);

    @Override
    public MpResponse request(MpRequest request) {
        try {
            HttpURLConnection connection = convertAndSend(request, request.getOptions());
            MpResponse mpResponse = convertResponse(connection);
            mpResponse.setData(new Decoder() {
            }.decode(mpResponse, (Type)Object.class));
            return mpResponse;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public <T> T request(MpRequest request, Class<T> clz) {
        try {
            HttpURLConnection connection = convertAndSend(request, request.getOptions());
            MpResponse mpResponse = convertResponse(connection);
            return new Decoder() {}.decode(mpResponse, clz);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    MpResponse convertResponse(HttpURLConnection connection) throws IOException {
        int status = connection.getResponseCode();
        String reason = connection.getResponseMessage();

        if (status < 0) {
            throw new IOException(format("Invalid status(%s) executing %s %s", status,
                    connection.getRequestMethod(), connection.getURL()));
        }

        Map<String, Collection<String>> headers = new LinkedHashMap<>();
        for (Map.Entry<String, List<String>> field : connection.getHeaderFields().entrySet()) {
            // response message
            if (field.getKey() != null) {
                headers.put(field.getKey(), field.getValue());
            }
        }

        Integer length = connection.getContentLength();
        if (length == -1) {
            length = null;
        }
        InputStream stream;
        if (status >= 400) {
            stream = connection.getErrorStream();
        } else {
            stream = connection.getInputStream();
        }

        MpResponse response = new MpResponse();
        response.setStatus(status);
        response.setResponseMessage(reason);
        response.setHeaders(headers);
        response.body(stream, length);
        return response;
    }


    HttpURLConnection convertAndSend(MpRequest request, MpRequest.Options options) throws Exception {
        final URL url = new URL(request.getUrl());
        final HttpURLConnection connection = this.getConnection(url);
        if (connection instanceof HttpsURLConnection) {
            HttpsURLConnection sslCon = (HttpsURLConnection) connection;
            SSLContext sslcontext = SSLContext.getInstance("TLS");
            sslcontext.init(null, new TrustManager[]{myX509TrustManager}, null);
            sslCon.setSSLSocketFactory(sslcontext.getSocketFactory());
        }
        connection.setConnectTimeout(options.connectTimeoutMillis());
        connection.setReadTimeout(options.readTimeoutMillis());
        connection.setAllowUserInteraction(false);
        connection.setInstanceFollowRedirects(options.isFollowRedirects());
        connection.setRequestMethod(request.getMethod().name());


        for (String field : request.getHeaders().keySet()) {
            for (String value : request.getHeaders().get(field)) {
                connection.addRequestProperty(field, value);
            }
        }
        connection.addRequestProperty("Accept", "*/*");
        if (request.body() != null) {
            connection.setDoOutput(true);
            OutputStream out = null;
            try {
                if(request.getBody().isFile()){
                    //边界
                    String boundary = "----------" + System.currentTimeMillis();
                    //边界
                    connection.addRequestProperty("Content-Type",
                            "multipart/form-data; boundary=" + boundary);
                    StringBuilder sb = new StringBuilder();
                    // 必须多两道线
                    sb.append("--")
                            .append(boundary)
                            .append("\r\n")
                            .append("Content-Disposition: form-data;name=\"media\";filename=\"")
                            .append(request.getBody().fileName())
                            .append("\" \r\n")
                            .append("Content-Type:application/octet-stream\r\n\r\n");
                    out = connection.getOutputStream();
                    // 输出表头
                    out.write(sb.toString().getBytes("utf-8"));
                    // 文件正文部分
                    out.write(request.body());
                    // 结尾部分
                    // 定义最后数据分隔线
                    byte[] foot = ("\r\n--" + boundary + "--\r\n").getBytes("utf-8");
                    //写结尾
                    out.write(foot);
                }else {
                    out = connection.getOutputStream();
                    out.write(request.body());
                }
            } finally {
                try {
                    out.close();
                } catch (IOException suppressed) {
                }
            }
        }
        return connection;
    }

    protected static TrustManager myX509TrustManager = new X509TrustManager() {

        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[]{};
        }

    };

    public HttpURLConnection getConnection(final URL url) throws IOException {
        return (HttpURLConnection) url.openConnection();
    }
}
